#ifndef SIMPLE_C_MACHINE_H
#define SIMPLE_C_MACHINE_H

#include "BinaryCode.h"
#include "List.h"

using namespace CoreLib::Basic;
using namespace SimpleC::Compiler;

namespace SimpleC
{
	
	const int DefaultStackSize = 10*1024*1024;

	class RuntimeException
	{
	public:
		String Message;
		RuntimeException(String message)
			:Message(message)
		{}
	};

	template<typename T>
	class BufferRef
	{
	private:
		T * buffer;
		int length;
	public:
		int Length()
		{
			return length;
		}
		T & operator[](int id)
		{
			if (id >= length)
				throw RuntimeException(L"index out of range when accessing argument list.");
			return buffer[id];
		}
		BufferRef(T * buffer, int len)
		{
			this->buffer = buffer;
			this->length = len;
		}
		int GetInt(int offset)
		{
			if (offset >= length)
				throw RuntimeException(L"index out of range when accessing argument list.");
			return *(int *)(buffer+offset);
		}
		double GetDouble(int offset)
		{
			if (offset >= length)
				throw RuntimeException(L"index out of range when accessing argument list.");
			return *(double *)(buffer+offset);
		}
		wchar_t GetChar(int offset)
		{
			if (offset >= length)
				throw RuntimeException(L"index out of range when accessing argument list.");
			return *(wchar_t *)(buffer+offset);
		}
		String * GetString(int offset)
		{
			if (offset >= length)
				throw RuntimeException(L"index out of range when accessing argument list.");
			return *(String * *)(buffer+offset);
		}
	};

	class WritableBuffer
	{
	private:
		unsigned char * stack;
		int * esp;
	public:
		WritableBuffer(unsigned char * stack, int * esp)
		{
			this->stack = stack;
			this->esp = esp;
		}
		void WriteInt(int val)
		{
			*(int *)(stack + *esp) = val;
			*esp += sizeof(int);
		}
		void WriteDouble(double val)
		{
			*(double *)(stack + *esp) = val;
			*esp += sizeof(double);
		}
		void WriteChar(wchar_t val)
		{
			*(wchar_t *)(stack + *esp) = val;
			*esp += sizeof(wchar_t);
		}
		void WriteString(String val)
		{
			*(String **)(stack + *esp) = new String(val);
			*esp += sizeof(String*);
		}
	};

	class ExternFunctionHandler
	{
	public:
		virtual bool HandleExternFunctionCall(String functionName, BufferRef<unsigned char> arguments, WritableBuffer & retVal) = 0;
	};

	class Machine
	{
	private:
		unsigned char * stack;
		int ebp, esp, eip;
		FunctionCode * curFunc;
		bool terminated;
		List<char> retVal;
		List<String> strStack;
		ProgramCode * program;
		int PopInt()
		{
			int res = *(int *)(stack + esp - sizeof(int));
			esp -= sizeof(int);
			return res;
		}
		void PushInt(int val)
		{
			*(int *)(stack + esp) = val;
			esp += sizeof(int);
		}
		
		void Instr_cPush(Instruction & instr);
		void Instr_iPush(Instruction & instr);
		void Instr_dPush(Instruction & instr);
		void Instr_sPush(Instruction & instr);
		void Instr_Pop(Instruction & instr);
		void Instr_sPop(Instruction & instr);
		void Instr_PushEmpty(Instruction & instr);
		void Instr_sAdd(Instruction & instr);
		void Instr_iAdd(Instruction & instr);
		void Instr_iSub(Instruction & instr);
		void Instr_iMul(Instruction & instr);
		void Instr_iDiv(Instruction & instr);
		void Instr_Mod(Instruction & instr);
		void Instr_dAdd(Instruction & instr);
		void Instr_dSub(Instruction & instr);
		void Instr_dMul(Instruction & instr);
		void Instr_dDiv(Instruction & instr);
		void Instr_And(Instruction & instr);
		void Instr_Or(Instruction & instr);
		void Instr_Xor(Instruction & instr);
		void Instr_BitAnd(Instruction & instr);
		void Instr_BitOr(Instruction & instr);
		void Instr_Not(Instruction & instr);
		void Instr_iNeg(Instruction & instr);
		void Instr_dNeg(Instruction & instr);
		void Instr_Inc(Instruction & instr);
		void Instr_Dec(Instruction & instr);
		void Instr_Rsh(Instruction & instr);
		void Instr_Lsh(Instruction & instr);
		void Instr_iEql(Instruction & instr);
		void Instr_iNeq(Instruction & instr);
		void Instr_iGeq(Instruction & instr);
		void Instr_iLeq(Instruction & instr);
		void Instr_iLess(Instruction & instr);
		void Instr_iGreater(Instruction & instr);
		void Instr_dEql(Instruction & instr);
		void Instr_dNeq(Instruction & instr);
		void Instr_dGeq(Instruction & instr);
		void Instr_dLeq(Instruction & instr);
		void Instr_dLess(Instruction & instr);
		void Instr_dGreater(Instruction & instr);
		void Instr_i2d(Instruction & instr);
		void Instr_d2i(Instruction & instr);
		void Instr_c2s(Instruction & instr);
		void Instr_iLoad(Instruction & instr);
		void Instr_dLoad(Instruction & instr);
		void Instr_cLoad(Instruction & instr);
		void Instr_sLoad(Instruction & instr);
		void Instr_LoadOffset(Instruction & instr);
		void Instr_sLoadOffset(Instruction & instr);
		void Instr_iStore(Instruction & instr);
		void Instr_dStore(Instruction & instr);
		void Instr_cStore(Instruction & instr);
		void Instr_sStore(Instruction & instr);
		void Instr_StoreOffset(Instruction & instr);
		void Instr_sStoreOffset(Instruction & instr);
		void Instr_Jump(Instruction & instr);
		void Instr_JumpTrue(Instruction & instr);
		void Instr_JumpFalse(Instruction & instr);
		void Instr_Call(Instruction & instr);
		void Instr_Ret(Instruction & instr);
		List<RefPtr<ExternFunctionHandler>> externHandlers;
	public:
		Machine(int stackSize = DefaultStackSize);
		~Machine()
		{
			delete[] stack;
		}
		void Run(ProgramCode * code);
		void AddExternFunctionHandler(ExternFunctionHandler * handler)
		{
			externHandlers.Add(handler);
		}
		void PrintStack()
		{
			printf("%d\n", *(int*)stack);
		}
	};
}
#endif